/*
 * Copyright (c) 2005, 2014 vacoor
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 */
package org.vacoor.nothing.security.service.impl.job;

import org.javasimon.aop.Monitored;
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.quartz.impl.triggers.CronTriggerImpl;
import org.quartz.listeners.JobListenerSupport;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.vacoor.nothing.common.reflect.MethodFinder;
import org.vacoor.nothing.common.util.Regexs;
import org.vacoor.nothing.common.util.StringUtils;
import org.vacoor.nothing.security.entity.Job;
import org.vacoor.nothing.security.service.IllegalJobCronException;
import org.vacoor.nothing.security.service.IllegalJobException;
import org.vacoor.nothing.security.service.IllegalJobTargetException;
import org.vacoor.nothing.security.service.JobManager;
import org.vacoor.nothing.web.service.support.CrudServiceImpl;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.text.ParseException;
import java.util.List;
import java.util.UUID;

import static org.vacoor.nothing.common.util.Nulls.nvl;

/**
 * 0/10 * * * * ?
 *
 * @author vacoor
 */
@Service
@Transactional
public class JobManagerImpl extends CrudServiceImpl<Job, Long> implements JobManager, InitializingBean, BeanNameAware, ApplicationContextAware {
    private static final String JOB_GROUP_NAME = "__JOB_GROUP__";
    private static final String TRIGGER_GROUP_NAME = "__TRIGGER_GROUP__";
    private static final String JOB_BEFORE_EXEC_STATE_KEY = "__JOB_BEFORE_EXEC_STATUS__";
    private static final String JOB_TARGET_KEY = "__JOB_TARGET__";
    private static final String JOB_EXEC_COUNT_KEY = "__JOB_EXEC_COUNT__";

    @Autowired
    private Scheduler scheduler;

    /**
     * 添加一个新的定时作业
     */
    @Override
    public void save(Job job) throws IllegalJobException {
        job.setJobState(Job.JobState.WAIT);
        job.setCount(0L);
        job.setFailCount(0L);

        super.save(job);
        doScheduleJob(job, false);
    }

    /**
     * 更新任务
     */
    @Override
    public void update(Job job) throws IllegalJobException {
        doUpdateJobIfNecessary(job);
        super.update(job);
    }

    /**
     * 删除作业
     */
    @Override
    public void delete(Job job) throws IllegalJobException {
        doDeleteJobIfNecessary(job.getId() + "");
        super.delete(job);
    }

    /**
     * 立即触发作业
     */
    @Override
    public void trigger(Long id) throws IllegalJobException {
        doTriggerJobIfNecessary(id + "");
    }

    /**
     * 暂停作业
     */
    @Override
    @Monitored
    public void pause(Long id) throws IllegalJobException {
        Job job = find(id);
        job.setJobState(Job.JobState.PAUSE);
        doPauseJobIfNecessary(id + "");
        // TODO UPDATE
    }


    /**
     * 恢复暂停的作业
     */
    @Override
    public void resume(Long id) throws IllegalJobException {
        Job job = find(id);
        job.setJobState(Job.JobState.WAIT);
        // TODO UPDATE
        doResumeJobIfNecessary(id + "");
    }


    public void setScheduler(Scheduler scheduler) {
        this.scheduler = scheduler;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        Assert.notNull(scheduler);
        ListenerManager listenerManager = scheduler.getListenerManager();
        if (null == listenerManager.getJobListener(jobListener.getName())) {
            listenerManager.addJobListener(jobListener, GroupMatcher.jobGroupEquals(JOB_GROUP_NAME));
        }

        JobManager proxy = getProxy();
        List<Job> jobs = proxy.findAll();
        for (Job job : jobs) {
            Job.JobState jobState = job.getJobState();
            if (Job.JobState.PAUSE == jobState || Job.JobState.STOP == jobState) {
                continue;
            }
            doScheduleJob(job, false);
        }
    }

    /* *************************************************************
     *
     * *************************************************************/
    private final JobListener jobListener = new JobListenerSupport() {
        @Override
        public String getName() {
            return "job-status-update-listener";
        }

        @Override
        public void jobToBeExecuted(JobExecutionContext context) {
            try {
                JobManager proxy = getProxy();
                JobDetail jobDetail = context.getJobDetail();
                JobDataMap jobDataMap = jobDetail.getJobDataMap();
                Long id = Long.parseLong(jobDetail.getKey().getName());
                Job job = proxy.find(id);

                Object countObj = jobDataMap.get(JOB_EXEC_COUNT_KEY);
                Long count = null != countObj ? Long.parseLong(countObj.toString()) : job.getCount();

                jobDataMap.put(JOB_BEFORE_EXEC_STATE_KEY, job.getJobState());
                jobDataMap.put(JOB_EXEC_COUNT_KEY, nvl(count, 0L) + 1);

                job.setCount(count);

                job.setJobState(Job.JobState.RUNNING);
                proxy.update(job);
            } catch (Exception ignore) {
            }
        }

        @Override
        public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
            try {
                JobManager proxy = getProxy();
                JobDetail jobDetail = context.getJobDetail();
                JobDataMap jobDataMap = jobDetail.getJobDataMap();
                Job.JobState state = (Job.JobState) jobDetail.getJobDataMap().get(JOB_BEFORE_EXEC_STATE_KEY);

                Long id = Long.parseLong(jobDetail.getKey().getName());
                Job job = proxy.find(id);

                // context.getPreviousFireTime();
                job.setLastExecTime(context.getFireTime());
                job.setNextExecTime(context.getNextFireTime());
                long elapsedTime = context.getJobRunTime();

                Long count = jobDataMap.getLong(JOB_EXEC_COUNT_KEY);
                Long failCount = nvl(job.getFailCount(), 0L);

                job.setCount(count);
                if (null != jobException) {
                    job.setFailCount(++failCount);
                }

                if (Job.JobState.RUNNING.equals(job.getJobState())) {
                    job.setJobState(state);
                }
                proxy.update(job);
            } catch (Exception ignore) {
            }
        }
    };

    /**
     * 添加一个定时作业
     *
     * @param job 定时作业信息
     * @throws IllegalJobException
     */
    protected void doScheduleJob(Job job, boolean override) throws IllegalJobException {
        try {
            CronExpression cron = new CronExpression(job.getCron());
            JobDetail jobDetail = createJobDetail(job);
            CronTrigger trigger = createTrigger(jobDetail, cron);

            // 如果覆盖的话
            if (override) {
                doDeleteJobIfNecessary(jobDetail.getKey().getName());
            }

            scheduler.scheduleJob(jobDetail, trigger);
            if (!scheduler.isStarted()) {
                scheduler.start();
            }
        } catch (ParseException e) {
            throw new IllegalJobCronException(e);
        } catch (SchedulerException e) {
            throw new IllegalJobException(e);
        }
    }

    /**
     * 更新一个定时作业
     *
     * @param job
     * @throws IllegalJobException
     */
    protected void doUpdateJobIfNecessary(Job job) throws IllegalJobException {
        final JobKey jobKey = getJobKey(job.getId() + "");
        final TriggerKey triggerKey = getTriggerKey(jobKey.getName());

        try {
            if (!scheduler.checkExists(jobKey)) {
                doScheduleJob(job, true);
            } else {
                CronExpression cron = new CronExpression(job.getCron());
                JobDetail jobDetail = scheduler.getJobDetail(jobKey);
                String target = jobDetail.getJobDataMap().getString(JOB_TARGET_KEY);
                Boolean concurrent = !jobDetail.isConcurrentExectionDisallowed();
                Trigger trigger = scheduler.getTrigger(triggerKey);
                CronTrigger newTrigger;

                // 如果调用目标或并发性变化, 需要重新创建
                if (null == target || !target.equals(job.getTarget()) || !concurrent.equals(job.getConcurrent())) {
                    doScheduleJob(job, true);
                } else if (!trigger.equals(newTrigger = createTrigger(jobDetail, cron))) {
                    // 只是触发器变化
                    scheduler.rescheduleJob(triggerKey, newTrigger);
                }
            }
        } catch (ParseException e) {
            throw new IllegalJobCronException(e);
        } catch (SchedulerException e) {
            throw new IllegalJobException(e);
        }
    }

    /**
     * 删除定时作业
     *
     * @param jobName
     * @throws IllegalJobException
     */
    protected void doDeleteJobIfNecessary(String jobName) throws IllegalJobException {
        final JobKey jobKey = getJobKey(jobName);
        final TriggerKey triggerKey = getTriggerKey(jobKey.getName());

        try {
            if (scheduler.checkExists(triggerKey)) {
                scheduler.pauseTrigger(triggerKey);
                scheduler.unscheduleJob(triggerKey);
            }
            if (scheduler.checkExists(jobKey)) {
                scheduler.pauseJob(jobKey);
                scheduler.deleteJob(jobKey);
            }
        } catch (SchedulerException e) {
            throw new IllegalJobException(e);
        }
    }

    /**
     * 触发作业 - 立即执行
     *
     * @param jobName
     * @throws IllegalJobException
     */
    protected void doTriggerJobIfNecessary(String jobName) throws IllegalJobException {
        final JobKey jobKey = getJobKey(jobName);
        try {
            if (scheduler.checkExists(jobKey)) {
                scheduler.triggerJob(jobKey);
            }
        } catch (SchedulerException e) {
            throw new IllegalJobException(e);
        }
    }

    /**
     * 暂停作业
     *
     * @param jobName
     * @throws IllegalJobException
     */
    protected void doPauseJobIfNecessary(String jobName) throws IllegalJobException {
        final JobKey jobKey = getJobKey(jobName);
        final TriggerKey triggerKey = getTriggerKey(jobKey.getName());

        try {
            if (scheduler.checkExists(triggerKey)) {
                scheduler.pauseTrigger(triggerKey);
            }
            if (scheduler.checkExists(jobKey)) {
                // scheduler.pauseJob(jobKey);
            }
        } catch (SchedulerException e) {
            throw new IllegalJobException(e);
        }
    }

    /**
     * 恢复已经暂停的作业
     *
     * @param jobName
     * @throws IllegalJobException
     */
    protected void doResumeJobIfNecessary(String jobName) throws IllegalJobException {
        final JobKey jobKey = getJobKey(jobName);
        final TriggerKey triggerKey = getTriggerKey(jobKey.getName());

        try {
            if (scheduler.checkExists(jobKey)) {
                scheduler.resumeJob(jobKey);
            }
            if (scheduler.checkExists(triggerKey)) {
                scheduler.resumeTrigger(triggerKey);
            }
        } catch (SchedulerException e) {
            throw new IllegalJobException(e);
        }
    }

    /* *************************************************************
     *
     * *************************************************************/

    /**
     * 根据给定的 cron 表达式创建一个 jobDetail 的触发器
     *
     * @param job  作业
     * @param cron cron 表达式
     * @throws ParseException
     */
    protected CronTrigger createTrigger(final JobDetail job, final CronExpression cron) throws ParseException {
        final TriggerKey triggerKey = getTriggerKey(job.getKey().getName());

        CronTriggerImpl cronTrigger = new CronTriggerImpl();
        cronTrigger.setKey(triggerKey);
        cronTrigger.setJobKey(job.getKey());
        cronTrigger.setJobDataMap(job.getJobDataMap());
        cronTrigger.setCronExpression(cron);
        return cronTrigger;
    }

    /**
     * 根据给定的 Job 创建对应的 Quartz JobDetail
     *
     * @param job 作业信息
     */
    protected JobDetail createJobDetail(final Job job) throws IllegalJobException {
        final String name = job.getId() + "";
        final String target = job.getTarget();
        final Boolean concurrent = nvl(job.getConcurrent(), true);
        final JobKey jobKey = getJobKey(name);

        // 构建 JobDetail
        BeanDefinitionBuilder jobDetailFactoryBean = BeanDefinitionBuilder.rootBeanDefinition("org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean");
        jobDetailFactoryBean.addPropertyValue("name", jobKey.getName());
        jobDetailFactoryBean.addPropertyValue("group", jobKey.getGroup());
        jobDetailFactoryBean.addPropertyValue("concurrent", concurrent);

        boolean isStatic;
        String beanName;        //
        String clazzName;
        String methodName;

        Object targetObject = null;
        Class<?> targetClass = null;

        // 尝试匹配调用方式
        String[][] matched = Regexs.grep(target, Job.BEAN_TARGET_PATTERN);
        if (0 < matched.length) {
            isStatic = false;   // 实例方法
            beanName = matched[0][1];
            clazzName = matched[0][2];
            methodName = matched[0][3];
        } else if (0 < (matched = Regexs.grep(target, Job.STATIC_TARGET_PATTERN)).length) {
            isStatic = true; // 静态方法
            beanName = null;
            clazzName = matched[0][1];
            methodName = matched[0][2];
        } else {
            // 非静态调用也非实例调用
            throw new IllegalJobTargetException("Invalid job target expression");
        }

        // 加载指定的调用调用目标类
        if (StringUtils.hasText(clazzName)) {
            try {
                targetClass = resolveClassName(clazzName);
            } catch (ClassNotFoundException e) {
                // 无效类
                throw new IllegalJobTargetException("Can not find class: " + clazzName, e);
            }
        }

        // 实例调用
        if (!isStatic && (StringUtils.hasLength(beanName) || null != targetClass)) {
            try {
                try {
                    targetObject = getBean(beanName, targetClass);
                } catch (BeansException e) {
                    // 没有找到对应 bean, 如果存在 targetClass 则尝试创建实例
                    if (null == targetClass) {
                        throw e;
                    }
                    targetObject = instantiate(targetClass, applicationContext, beanName);
                }
            } catch (RuntimeException e) {
                // 无法根据信息创建
                StringBuilder buff = new StringBuilder();
                buff.append("Can not ");
                if (StringUtils.hasLength(beanName)) {
                    buff.append("find bean '").append(beanName).append(null != targetClass ? "' and can not " : "'");
                }
                if (null != targetClass) {
                    buff.append("instantiate class '").append(targetClass.getName()).append("'");
                }
                throw new IllegalJobTargetException(buff.toString(), e);
            }
            targetClass = null != targetClass ? targetClass : targetObject.getClass();
        }

        // 实例调用/静态调用都不允许为null
        if (null == targetClass) {
            throw new IllegalJobTargetException("No valid class set");
        }

        // 验证方法
        try {
            if (isStatic) {
                MethodFinder.findStaticMethod(targetClass, false, methodName);
            } else {
                MethodFinder.findInstanceMethod(targetClass, false, methodName);
            }
        } catch (NoSuchMethodException nse) {
            throw new IllegalJobTargetException("No such method: " + target, nse);
        }

        jobDetailFactoryBean.addPropertyValue("targetClass", targetClass);
        jobDetailFactoryBean.addPropertyValue("targetObject", targetObject);
        jobDetailFactoryBean.addPropertyValue("targetMethod", methodName);
        // if (isStatic) jobDetailFactoryBean.addPropertyValue("staticMethod", targetClass + "." + methodName);

        // 注册初始化后获取
        BeanDefinitionRegistry beanRegistry = (BeanDefinitionRegistry) applicationContext.getAutowireCapableBeanFactory();
        try {
            beanRegistry.registerBeanDefinition(jobKey.getName(), jobDetailFactoryBean.getBeanDefinition());
        } catch (BeansException e) {
            throw new IllegalJobException("create job fail", e);
        }
        JobDetail jobDetail = applicationContext.getBean(jobKey.getName(), JobDetail.class);
        jobDetail.getJobDataMap().put(JOB_TARGET_KEY, job.getTarget());
        return jobDetail;
    }

    /* ****************************************************
     *
     * ****************************************************/

    /**
     * 实例化给定类
     * 1. 如果没有指定 applicationContext 或 applicationContext 的 beanFactory 没有实现 BeanDefinitionRegistry 则直接 newInstance
     * 2. 如果指定 applicationContext 且 applicationContext 实现 BeanDefinitionRegistry 则使用动态注册来实例化(处理spring 注入等)
     * 如果没有
     *
     * @param clazz
     * @param ctx
     * @param beanName
     * @return
     */
    protected Object instantiate(Class<?> clazz, ApplicationContext ctx, String beanName) {
        AutowireCapableBeanFactory beanFactory = null != ctx ? ctx.getAutowireCapableBeanFactory() : null;
        if (null == ctx || !(beanFactory instanceof BeanDefinitionRegistry)) {
            return BeanUtils.instantiate(clazz);
        }

        BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(clazz);
        // 在 spring 中注册bean, 以便处理自动注入等等问题
        String name = null != beanName ? beanName : UUID.randomUUID().toString();
        try {
            registry.registerBeanDefinition(name, builder.getBeanDefinition());
            return ctx.getBean(name, clazz);
        } catch (RuntimeException e) {
            // 如果指定了 beanName 则异常时移除
            if (null != beanName) registry.removeBeanDefinition(name);
            throw e;
        } finally {
            // 如果没有指定 beanName 则创建后移除
            if (null == beanName) registry.removeBeanDefinition(name);
        }
    }

    protected <T> T getBean(String beanName) throws BeansException {
        return getBean(beanName, null);
    }

    protected <T> T getBean(Class<T> type) throws BeansException {
        return getBean(null, type);
    }

    /**
     * 首先从当前 applicationContext 中获取, 如果没有从 Spring Web MVC application Context 中获取
     *
     * @param beanName bean 名称
     * @param type     bean class
     * @param <T>      bean 类型
     */
    protected <T> T getBean(String beanName, Class<T> type) throws BeansException {
        try {
            return getBean(applicationContext, beanName, type);
        } catch (BeansException e) {
            RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
            // DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE)
            WebApplicationContext ctx = (WebApplicationContext) requestAttributes.getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", RequestAttributes.SCOPE_REQUEST);
            if (null != ctx) {
                try {
                    return getBean(ctx, beanName, type);
                } catch (BeansException ignore) { /*ignore*/ }
            }
            throw e;
        }
    }

    @SuppressWarnings("unchecked")
    private <T> T getBean(BeanFactory beanFactory, String beanName, Class<T> type) {
        if (null == beanFactory) {
            throw new IllegalArgumentException("beanFactory 必须不为null");
        }
        if (null == beanName && null == type) {
            throw new IllegalArgumentException("bean name and type 至少有一个不为null");
        }
        if (null == beanName) {
            return beanFactory.getBean(type);
        }
        if (null == type) {
            return (T) beanFactory.getBean(beanName);
        }
        return beanFactory.getBean(beanName, type);
    }

    protected Class<?> resolveClassName(String className) throws ClassNotFoundException {
        return ClassUtils.forName(className, ClassUtils.getDefaultClassLoader());
    }

    private JobKey getJobKey(String name) {
        return JobKey.jobKey(name, JOB_GROUP_NAME);
    }

    private TriggerKey getTriggerKey(String jobName) {
        return TriggerKey.triggerKey(jobName + "_trigger", TRIGGER_GROUP_NAME);
    }


    private ApplicationContext applicationContext;
    private String beanName;

    private JobManager getProxy() {
        return applicationContext.getBean(beanName, JobManager.class);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void setBeanName(String name) {
        this.beanName = name;
    }

    public static void main(String[] args) throws SQLException {
        String url = "jdbc:mysql://121.40.102.225:3306/WorkerInformation?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&autoReconnectForPools=true&failOverReadOnly=false";
        Connection conn = DriverManager.getConnection(url, "root", "rot");
        System.out.println(conn);
    }
}
